Scoping and Constants

Variable Scope in ECMAScript 5

You can use var to define variables in ECMAScript 5. A function defines a new scope for the variables.


In [6]:
var x = 42;

function globalX() {
    return x;
}

function localX() {
    var x = 2017;
    return x;
}

console.log(globalX());
console.log(localX());


42
2017
Out[6]:
undefined

Functions are the only language constructs in ECMAScript 5 which defines a new variable scope. If you want to create a block with its own variable scope you'll use an IIFE (Immediately-Invoked Function Expression). An IIFE defines a function which creates its own variable scope and invokes it immediately.


In [9]:
function scopedX() {
    var x = 42;
    
    (function() {
        var x = 2017;
        console.log(x);
    })();
    
    console.log(x);
}

scopedX();


2017
42
Out[9]:
undefined

Variable Hoisting

Variables which are defined with var get hoisted in JavaScript. Let's look at the following example:


In [10]:
var x = 42;

function hoistedX(flag) {
    if (flag) {
        var x = 2017;
        return x;
    }
    
    return x;
}

console.log(hoistedX(true));
console.log(hoistedX(false));


2017
undefined
Out[10]:
undefined

What would you expect as output? The first call returns 2017 which you can expect from the code. But the second call returns undefined! Why? Shouldn't it return 42?

I mentioned that only functions create new variable scopes in ECMAScript 5. The function hoistedX creates a new scope, the if statement does not. Actually, the code above is equivalent to this one:


In [11]:
var x = 42;

function hoistedX(flag) {
    var x;
    
    if (flag) {
        x = 2017;
        return x;
    }
    
    return x;
}

console.log(hoistedX(true));
console.log(hoistedX(false));


2017
undefined
Out[11]:
undefined

The if statement does not create a new variable scope. Therefore the return statement outside the if block returns the same x variable like the one in the if block. The only difference is that the if block initializes the variable before it returns it. Although the x variable is declared in the if block it gets "hoisted" to the scope of the enclosing function.

Block Scoping in ECMAScript 2015

ECMAScript 2015 introduces block scoping. You can use let to create a variable which is scoped to the block which surrounds it.


In [12]:
var x = 42;

function blockScopedX(flag) {
    if (flag) {
        let x = 2017;
        return x;
    }
    
    return x;
}

console.log(blockScopedX(true));
console.log(blockScopedX(false));


2017
42
Out[12]:
undefined

With let instead of var the function outputs "2017" and "42" like we would expect.

What happens when you try to access the variable before it is declared?


In [1]:
let z = 42;
if (true) {
    console.log(z);
    let z = 2017;
    console.log(z);
}
console.log(z);


ReferenceError: z is not defined
    at evalmachine.<anonymous>:3:17
    at ContextifyScript.Script.runInThisContext (vm.js:26:33)
    at Object.exports.runInThisContext (vm.js:79:17)
    at run ([eval]:608:19)
    at onRunRequest ([eval]:379:22)
    at onMessage ([eval]:347:17)
    at emitTwo (events.js:106:13)
    at process.emit (events.js:191:7)
    at process.nextTick (internal/child_process.js:752:12)
    at _combinedTickCallback (internal/process/next_tick.js:67:7)

This leads to a ReferenceError. The variable z is created as soon as the block is entered. Until the variable is declared (with let) you cannot access it. You cannot read it and you cannot set it. This is called temporal dead zone.

With var a variable is created, too, as soon as the block is entered. But it is set to undefined as long as you don't assign a value to it ("variable hoisting").

Constants

You can define constants in ECMAScript 2015 with the const keyword.


In [3]:
const PI = 3.1415926;
let r = 4.2;
console.log('Perimeter = ' + 2 * PI * r);


Perimeter = 26.38937784
Out[3]:
undefined

Variables declared with const cannot be changed; they are immutable. This does not imply that the value which is assigned to the variable cannot be changed.


In [4]:
const obj = {};
obj.prop = 42;
console.log(obj.prop);


42
Out[4]:
undefined

As you can see you can assign new properties to the object which the constant variable obj points to. You cannot change the reference to that object, but you can change the object itself.

var versus let versus const

Which one should you use in your code? I recommend to use either let or const.

  1. Prefer const. That should be your default. You cannot change the variable later. This can prevent a whole class of errors.
  2. Use let if the initial value of the variable can be changed later on.
  3. Avoid var. There is no reason to use it anymore.

Summary

  • The scoping rules in JavaScript versions before ECMAScript 2015 were cumbersome. Besides the global context, only functions created a new variable scope. Furthermore variable hoisting could lead to some confusion when reading JavaScript code.
  • ECMAScript 2015 introduces two new ways to declare variables: let and const. The block surrounding the variable declarations define the scope for variables declared with let or const.
  • Prefer const to avoid changing a variable unintentionally. Use let if you want to declare a variable which changes later on. Avoid var.

New Literals

A literal is a syntactic construct which produces a value. For example an integer literal produces an integer. A string literal produces a string and so on.


In [1]:
42


Out[1]:
42

In [3]:
'The answer to the ultimate question of life, the universe, and everything.'


Out[3]:
'The answer to the ultimate question of life, the universe, and everything.'

ECMAScript 2015 introduces new integer literals and template literals.

New Integer Literals

You could write integer literals in two ways before ECMAScript 2015:


In [4]:
42       // decimal integer literal


Out[4]:
42

In [5]:
0xFF     // hexadecimal integer literal


Out[5]:
255

There are two new kinds of integer literals in ECMAScript 2015: binary literals (prefixed with 0b or 0B) and octal literals (prefixed with 0o or 0O).


In [6]:
0b10011001   // binary literal


Out[6]:
153

In [7]:
0o42         // octal literal


Out[7]:
34

Template Literals

String Interpolation

If you use ECMAScript 5 and you want to insert values into a string, you can use string concatenation.


In [9]:
function printName(firstName, lastName) {
    console.log(firstName + ' ' + lastName);
}

printName('Andreas', 'Schlapsi');


Andreas Schlapsi
Out[9]:
undefined

ECMAScript 2015 introduces template literals which support string interpolation. You write the interpolated expression between ${ and }. Template literals are delimited by backticks (`).


In [10]:
function printName(firstName, lastName) {
    console.log(`${firstName} ${lastName}`);
}

printName('Andreas', 'Schlapsi');


Andreas Schlapsi
Out[10]:
undefined

The interpolated expression can be a more complex JavaScript expression than just a variable reference.


In [12]:
function printSum(a, b) {
    console.log(`${a + b}`);
}

printSum(40, 2);


42
Out[12]:
undefined

Multi-line strings

If you want to build multi-line strings in ECMAScript 5, you could concatenate each line or you could escape the newlines.


In [15]:
var divElement = 
    '<div class="post">\n' +
    '  <h1>Title</h1>\n' +
    '</div>';

console.log(divElement);


<div class="post">
  <h1>Title</h1>
</div>
Out[15]:
undefined

In [16]:
var divElement = '\
    <div class="post">\n\
        <h1>Title</h1>\n\
    </div>';

console.log(divElement);


    <div class="post">
        <h1>Title</h1>
    </div>
Out[16]:
undefined

The second variant looks a little bit nicer. You need to escape the newlines, though, and it contains more spaces than the first variant.

ECMAScript 2015's template literals are even nicer.


In [1]:
const divElement = `
    <div class="post">
        <h1>Title</h1>
    </div>`;

console.log(divElement);


    <div class="post">
        <h1>Title</h1>
    </div>
Out[1]:
undefined

It's easy to embed other values with template literals.


In [1]:
const title = 'Awesome page';
const divElement = `
    <div class="post">
        <h1>${title}</h1>
    </div>`;

console.log(divElement);


    <div class="post">
        <h1>Awesome page</h1>
    </div>
Out[1]:
undefined

Summary

  • ECMAScript 2015 introduces two new integer literals: the binary and octal integer literals.
  • Template literals provide a way to do string interpolation and multi-line strings in JavaScript.

Improved Unicode Support

Unicode code point escapes

A string in JavaScript is a sequence of UTF-16 code units. You can use a hex escape to include a character from the first 255 code points in a string.


In [2]:
console.log('\x41');


A
Out[2]:
undefined

If you want to use a character with a 16-bit code point (first plane or basic multilingual plane - BMP), you can use a unicode escape.


In [3]:
console.log('\u1234');


Out[3]:
undefined

Unicode defines other planes (supplementary planes) and to reach that with UTF-16 you can use surrogate pairs. You have to calculate them self.


In [4]:
console.log('\uD83D\uDE00');


😀
Out[4]:
undefined

ECMAScript 2015 introduces Unicode code point escapes. You don't have to calculate the surrogate pairs anymore if you use them.


In [6]:
console.log('\u{1F600}');


😀
Out[6]:
undefined

The code point is delimited by \u{ and } and is represented by a 6-digit hexadecimal number.

Regular Expressions

The . matches any character in a regular expression. Regular expressions handle surrogate pairs as two subsequent characters.


In [1]:
const text = '(\u{1F600})';
/\(.\)/.test(text);


Out[1]:
false

ECMAScript 2015 introduces the u flag which enables special Unicode handling for regular expressions.


In [1]:
const text = '(\u{1F600})';
/\(.\)/u.test(text);


Out[1]:
true

This flag enables the special Unicode handling for other patterns, too.

Summary

  • ECMAScript 2015 introduces Unicode code point escapes which enable you to use character from supplementary planes without calculating surrogate pairs by hand.
  • Regular Expressions have a new u flag which enables better support for Unicode characters from supplementary planes.

The post "JavaScript has a Unicode problem" (by Mathias Bynens) explains the new Unicode features in ECMAScript 2015 in more details.

Destructuring

Destructuring is a way to extract multiple values from objects or Arrays. You use patterns in locations that receive data like left-hand side of an assignment to specify how the values are extracted.

Object Destructuring

Suppose we have an object with multiple properties and you want to assign the values of these properties to variables. In ECMAScript 5 you had to assign the properties to the variables separately.


In [1]:
var obj = { a: 1, b: 2, c: 'the answer' };
var value1 = obj.a;
var value2 = obj.b;
var value3 = obj.c;

console.log(value1, value2, value3);


1 2 'the answer'
Out[1]:
undefined

In ECMAScript 2015 you can use object destructuring to do this in one line.


In [1]:
const obj = { a: 1, b: 2, c: 'the answer' };
const {a: value1, b: value2, c: value3} = obj;

console.log(value1, value2, value3);


1 2 'the answer'
Out[1]:
undefined

The pattern is written in the form { <property name>: <variable name>, ... }. In the example above the pattern {a: value1, b: value2, c: value3} means that the value of property a is assigned to the variable value1, the value of property b is assigned to the variable value2, and so on.

If the property name and the variable name are the same you can just write the property name.


In [1]:
const obj = { a: 1, b: 2, c: 'the answer' };
// Instead of this:
// let {a: a, b: b, c: c} = obj;
// you can write this:
let {a, b, c} = obj;

console.log(a, b, c);


1 2 'the answer'
Out[1]:
undefined

Default Values

Default values can be provided with { <property name>: <variable name> = <default value>, ... }.


In [1]:
const obj = { a: 1, b: 2, c: 'the answer' };
let {a, b, c, d: d = 'is 42'} = obj;

console.log(a, b, c, d);


1 2 'the answer' 'is 42'
Out[1]:
undefined

Default values work with the short form, too.


In [1]:
const obj = { a: 1, b: 2, c: 'the answer' };
let {a, b, c, d = 'is 42'} = obj;

console.log(a, b, c, d);


1 2 'the answer' 'is 42'
Out[1]:
undefined

Default values are calculated on demand.


In [1]:
function getVal1() {
    console.log('getVal1');
    return 'a';
}

function getVal2() {
    console.log('getVal2');
    return 'b';
}

const {val1 = getVal1(), val2 = getVal2()} = { val1: 42 };

console.log(val1, val2);


getVal2
42 'b'
Out[1]:
undefined

Extracting Values from Nested Objects

Object destructuring can extract values from nested objects.


In [2]:
const obj = { a: { x: 42, y: 89, z: 123 }, b: { g: 'a', h: 'b', i: 'c' } };
const { a: { x, y, z }, b: { h } } = obj;

console.log(x, y, z, h);


42 89 123 'b'
Out[2]:
undefined

This works with default values, too.


In [1]:
const obj = { a: { x: 42, y: 89, z: 123 }, b: { g: 'a', h: 'b', i: 'c' } };
const { a: { x, y, z }, b: { h = 'a', j = 'x' } } = obj;

console.log(x, y, z, h, j);


42 89 123 'b' 'x'
Out[1]:
undefined

Watch out if you mix default values with nested objects. If you provide an object as default value it is not merged with the actual extracted object.


In [1]:
const obj = { a: { x: 42, y: 89, z: 123 }, b: { g: 'a', h: 'b', i: 'c' } };
const { a: { x, y, z }, c: { h, j } = { h: 1, j: 2 } } = obj;

console.log(x, y, z, h, j);


42 89 123 1 2
Out[1]:
undefined

In [1]:
const obj = { a: { x: 42, y: 89, z: 123 }, b: { g: 'a', h: 'b', i: 'c' } };
const { a: { x, y, z }, b: { h, j } = { h: 1, j: 2 } } = obj;

console.log(x, y, z, h, j);


42 89 123 'b' undefined
Out[1]:
undefined

Array Destructuring

Destructuring works with Arrays, too. The elements of an Array are assigned to the specified variable names.


In [2]:
let iterable = [1, 2, 3, 4];
let [a, b, c, d] = iterable;

console.log(a, b, c, d);


1 2 3 4
Out[2]:
undefined

You don't have to extract all values.


In [1]:
let iterable = [1, 2, 3, 4];
let [a, b] = iterable;

console.log(a, b);


1 2
Out[1]:
undefined

The right-hand side must be an iterable. The following examples throw a TypeError:


In [2]:
let [x] = {};      // An empty object is not iterable
let [y] = undefined;
let [z] = null;


TypeError: undefined is not a function
    at evalmachine.<anonymous>:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:26:33)
    at Object.exports.runInThisContext (vm.js:79:17)
    at run ([eval]:608:19)
    at onRunRequest ([eval]:379:22)
    at onMessage ([eval]:347:17)
    at emitTwo (events.js:106:13)
    at process.emit (events.js:191:7)
    at process.nextTick (internal/child_process.js:752:12)
    at _combinedTickCallback (internal/process/next_tick.js:67:7)

Rest Operator

The rest operator catches all remaining values in a new Array.


In [1]:
let iterable = [1, 2, 3, 4];
let [a, ...r] = iterable;

console.log(a, r);


1 [ 2, 3, 4 ]
Out[1]:
undefined

Elision

You can skip array elements with an elision.


In [1]:
let iterable = [1, 2, 3, 4];
let [,,c, ...r] = iterable;

console.log(c, r);


3 [ 4 ]
Out[1]:
undefined

Where You Can Use Destructuring?

You can use destructuring in the following constructs:

  1. Variable declarations with var, let, or const
  2. Assignments
  3. Parameter definitions

In [1]:
// 1. Variable declarations
var { prop1, prop2 } = { prop1: 1, prop2: 2 };
let [a, b] = [1, 2];
const { x, y } = { x: 42, y: 84 };

console.log('Variable declarations:');
console.log(prop1, prop2, a, b, x, y);

// 2. Assignments
({ a, b, x1 = 12, y1 = 24 } = { a: prop1, y1: x });
[ g, ...h ] = [42];

console.log('Assignments:');
console.log(a, b, x1, y1, g, h);

// 3. Parameter definitions
function printCoords ({ x, y, z = 0 }) {
    console.log(x, y, z);
}

console.log('Parameter definitions:');
printCoords({ x: 4, y: 2 });


Variable declarations:
1 2 1 2 42 84
Assignments:
1 undefined 12 42 42 []
Parameter definitions:
4 2 0
Out[1]:
undefined

Note that a statement cannot start with a curly brace ({). If you use object destructuring in an assignment you'll probably have to wrap the statement in parentheses.

More Complex Assignment Targets

You don't have to use just a simple variable name as assignment target. It could also be an array element or an object property.


In [1]:
const arr = [], obj = {};
({ prop: arr[0] } = { prop: '42' });
[ obj.head, ...obj.tail ] = [1, 2, 3, 4];

console.log(arr);
console.log(obj);


[ '42' ]
{ head: 1, tail: [ 2, 3, 4 ] }
Out[1]:
undefined

Summary

Destructuring is an interesting syntactic construct which lets you extract values from objects and arrays. It is particularly interesting for extracting the individual components of return values.

Symbols

Symbols are a new primitive data type in ECMAScript 2015. You create a symbol with a factory function.


In [2]:
const RED1 = Symbol('red');
const RED2 = Symbol('red');

console.log(RED1 === RED2, RED1 === RED1, RED2 === RED2);
console.log(RED1 == RED2, RED1 == RED1, RED2 == RED2);


false true true
false true true
Out[2]:
undefined

Some properties of symbols:

  • The value created by the factory function (Symbol) is unique.
  • Two symbol values are equal if they were created by the same call to the factory function.
  • The string passed to the factory function is not used for any purpose. It is just a description.
  • Symbol values are not automatically coerced in general to other types like string or number.

While a symbol is not automatically coerced to other types, you can convert it explicitly to strings or booleans.


In [1]:
const RED = Symbol('red');

console.log(String(RED));
console.log(RED.toString());
console.log(Boolean(RED));
console.log(!RED);


Symbol(red)
Symbol(red)
true
false
Out[1]:
undefined

As you can see, when you negate a symbol with !, the symbol is implicitly converted. Coercion does not work in other cases.

But what can you actually do with that new type?

Use Cases

Constants which represent concepts

You can use symbols for constants which represent concepts. Without symbols you would probably use string or integer constants.


In [1]:
const RED = Symbol('red');
const GREEN = Symbol('green');
const BLUE = Symbol('blue');


Out[1]:
undefined

Keys of non-public properties

You can use symbols for property keys which are supposed to be private. They cannot be accessed when the symbol is not in scope.


In [1]:
const _private = Symbol('private property');
const obj = {
    data: 'this is the data',
    [_private]() {
        console.log('private method', this.data);
    }
}

obj[_private]();
console.log(JSON.stringify(obj));
console.log(Object.keys(obj));
console.log(Object.getOwnPropertyNames(obj));
console.log(Object.getOwnPropertySymbols(obj));
console.log(Reflect.ownKeys(obj));


private method this is the data
{"data":"this is the data"}
[ 'data' ]
[ 'data' ]
[ Symbol(private property) ]
[ 'data', Symbol(private property) ]
Out[1]:
undefined

As you can see, you can get access to the symbol via Object.getOwnPropertySymbols() and Reflect.ownKeys(). Symbols do not protect you from unauthorized access.

We will see in the article about iterators that ECMAScript 2015 defines the symbol Symbol.iterator. You can use it for an iterator method of the object.

Summary

  • Symbols are unique values generated by a factory function.
  • You can use them for constants which represent concepts.
  • You can use them as keys for non-public object properties.

Resources

Functions

Arrow Functions

ECMAScript 2015 introduces a new syntax for creating functions: arrow functions.


In [1]:
const square = x => x * x;
const square2 = function(x) { return x * x };

console.log(square(4));
console.log(square2(2));


16
4
Out[1]:
undefined

An arrow function is more concise than the old way (square2). You don't have to use the function keyword and you need to write the return statement for returning a function result.

Arrow functions return the result of the last expression automatically. You write an arrow (=>) between the parameter list and the function body. If the function has more than one arguments, you need to enclose the parameter list in parentheses.


In [1]:
const add = (a, b) => a + b;

console.log(add(4, 5));


9
Out[1]:
undefined

this in JavaScript

The keyword this behaves unexpectedly in JavaScript. Let's look at an example.


In [5]:
function Adder(summand) {
    this.summand = summand;
}

Adder.prototype.add = function(numbers) {
    return numbers.map(function(n) {
        return this.summand + n;
    });
};

var add2 = new Adder(2);
console.log(add2.add([1, 2, 3, 4]));


[ NaN, NaN, NaN, NaN ]
Out[5]:
undefined

That is not the result we would expect. The problem is that this does not point to correct object. The value of this depends in JavaScript on the value of this in the scope of the calling function. The function which we pass to map is called in the map function. this points to the numbers array in that scope. summand does not exist there, which is why the result is Nan for each sum.

How can we fix this?

A solution which is often found in ECMAScript 5 code is to store the correct value of this in an variable which can be accessed from the function via a closure.


In [7]:
function Adder(summand) {
    this.summand = summand;
}

Adder.prototype.add = function(numbers) {
    var that = this;
    
    return numbers.map(function(n) {
        return that.summand + n;
    });
};

var add2 = new Adder(2);
console.log(add2.add([1, 2, 3, 4]));


[ 3, 4, 5, 6 ]
Out[7]:
undefined

Now the solution works as expected.

Another soluton is to bind the value of this via the bind method.


In [8]:
function Adder(summand) {
    this.summand = summand;
}

Adder.prototype.add = function(numbers) {
    return numbers.map(function(n) {
        return this.summand + n;
    }.bind(this));
};

var add2 = new Adder(2);
console.log(add2.add([1, 2, 3, 4]));


[ 3, 4, 5, 6 ]
Out[8]:
undefined

That works, too.

And a third solution for map is passing the value of this as a second argument.


In [9]:
function Adder(summand) {
    this.summand = summand;
}

Adder.prototype.add = function(numbers) {
    return numbers.map(function(n) {
        return this.summand + n;
    }, this);
};

var add2 = new Adder(2);
console.log(add2.add([1, 2, 3, 4]));


[ 3, 4, 5, 6 ]
Out[9]:
undefined

Arrow functions in ECMAScript 2015 bind the value of this automatically to the correct value.


In [12]:
function Adder(summand) {
    this.summand = summand;
}

Adder.prototype.add = function(numbers) {
    return numbers.map(n => {
        return this.summand + n
    });
};

var add2 = new Adder(2);
console.log(add2.add([1, 2, 3, 4]));


[ 3, 4, 5, 6 ]
Out[12]:
undefined

If we use all ECMAScript 2015 features in our class, the code looks like this:


In [1]:
class Adder {
    constructor(summand) {
        this.summand = summand;
    }
    
    add(numbers) {
        return numbers.map(n => this.summand + n);
    }
}

const add4 = new Adder(4);
console.log(add4.add([1, 2, 3, 4]));


[ 5, 6, 7, 8 ]
Out[1]:
undefined

By the way, you could also use the following method to achieve the same result as we did with the class before.


In [2]:
const adder = a => b => a + b;
const add5 = adder(5);

[1, 2, 3, 4].map(add5);


Out[2]:
[ 6, 7, 8, 9 ]

The function adder accepts one argument and returns a function which accepts another argument.

Function Parameters

Rest Parameter

argument in ECMAScript 5

In JavaScript exists the special variable arguments which you can use to access all arguments passed to the function. It looks like an array, but it isn't. It contains an entry for each argument passed to the function. The length property contains the number of arguments passed to the function and hence the number of entries in arguments.


In [6]:
function log() {
    for (var i = 0; i < arguments.length; i++) {
        console.log(i, arguments[i]);
    }
}

log('a', 'b', 'c');


0 'a'
1 'b'
2 'c'
Out[6]:
undefined

But it isn't an array which means that you cannot use the Array functions for accessing function parameters.


In [9]:
function log() {
    // this throws a TypeError: "arguments.forEach is not a function"
    arguments.forEach(function (item, index) {
        console.log(index, item);
    });
}

log('a', 'b', 'c');


TypeError: arguments.forEach is not a function
    at log (evalmachine.<anonymous>:3:15)
    at evalmachine.<anonymous>:8:1
    at ContextifyScript.Script.runInThisContext (vm.js:26:33)
    at Object.exports.runInThisContext (vm.js:79:17)
    at run ([eval]:608:19)
    at onRunRequest ([eval]:379:22)
    at onMessage ([eval]:347:17)
    at emitTwo (events.js:106:13)
    at process.emit (events.js:191:7)
    at process.nextTick (internal/child_process.js:752:12)

You can use the slice method to convert arguments to an array and access the function arguments like an array.


In [11]:
function log() {
    var args = [].slice.call(arguments);
    // or:
    // var args = Array.prototype.slice.call(arguments);
    args.forEach(function(item, index) {
        console.log(index, item);
    });
}

log('a', 'b', 'c');


0 'a'
1 'b'
2 'c'
Out[11]:
undefined

ECMAScript 2015 introduces the rest operator (...). You can apply it before argument which should get all remaining function arguments.


In [13]:
function log(...args) {
    args.forEach((item, index) => {
        console.log(index, item);
    });
}

log('a', 'b', 'c');


0 'a'
1 'b'
2 'c'
Out[13]:
undefined

The rest parameter must be the last one and it collects all arguments which aren't assigned to the arguments before.


In [21]:
function log(message, ...args) {
    console.log(message);
    args.forEach(item => {
        console.log('    -', item);
    });
}

log('The numbers from 1 to 5:', 1, 2, 3, 4, 5);


The numbers from 1 to 5:
    - 1
    - 2
    - 3
    - 4
    - 5
Out[21]:
undefined

Spread operator

The opposite of the rest operator is the spread operator. You can use it when you call a function and the function arguments are available as array. The spread operator looks exactly like the rest operator, but it is used when you call a function.


In [22]:
function add(a, b, c) {
    return a + b + c;
}

const args = [1, 2, 3];
console.log(add(...args));


6
Out[22]:
undefined

You can use the spread operator also if you want to insert the elements of an array into a new array.


In [1]:
const arr1 = [4, 5, 6, 7];
const arr2 = [1, 2, 3, ...arr1, 8, 9, 10];

console.log(arr2);


[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
Out[1]:
undefined

Default Parameter

Another feature concerning function arguments are default parameters. You can define default values for parameters which will be used if no arguments are provided when the function is called.


In [20]:
function point(x=0, y=0, z=0) {
    return {x: x, y: y, z: z};
}

console.log(point());
console.log(point(4, 2));
console.log(point(8, 5, 2));


{ x: 0, y: 0, z: 0 }
{ x: 4, y: 2, z: 0 }
{ x: 8, y: 5, z: 2 }
Out[20]:
undefined

Using Destructuring for Functions

Simulating named parameters

You can use destructuring to simulate named parameters.


In [2]:
function print({text, appendNewLine=false, indentLevel=0}) {
    console.log(text, appendNewLine, indentLevel);
}

print({text: 'This is the text.', indentLevel: 8});


This is the text. false 8
Out[2]:
undefined

Simulating multiple return values

Another way to use destructuring with functions is for simulating multiple return values.


In [2]:
function doSomething({condition1=true, condition2=true}) {
    let succeeded = condition1 && condition2;
    let result = Math.random();
    
    return {succeeded: succeeded, result: result};
}

let {succeeded, result} = doSomething({condition1: true, condition2: false});
if (succeeded) {
    console.log(`Result: ${result}!`);
} else {
    console.log('Failed!');
}

({succeeded, result} = doSomething({condition1: true, condition2: true}));
if (succeeded) {
    console.log(`Result: ${result}!`);
} else {
    console.log('Failed!');
}


Failed!
Result: 0.7809295382948631!
Out[2]:
undefined

Summary

  • Arrow functions introduces a new nicer syntax for defining functions and fix this when passing a function as parameter to another function (higher order function).
  • Rest parameters can make accessing the infamous arguments special variable obsolete. It collects all remaining parameters and assigns them to an array.
  • The spread operator is the opposite of the rest operator, although they look the same. The spread operator spreads the elements of an array and assigns them to the arguments of the argument list of a function.
  • You can use default values for function parameters in ECMAScript 2015.
  • You can use destructuring to simulate named parameters and multiple return values.

In [ ]: